package org.grenobus.html;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

public class SimpleHTML {

	private String fTag;
	private SimpleHTML fParent;
	private List<SimpleHTML> fChildren = new ArrayList<SimpleHTML>();
	private String fText;

	class Attr {
		String name;
		String value;
		
		Attr(String n, String v) {
			name = n;
			value = v;
		}
	}
	
	private List<Attr> fAttrs; 

	public SimpleHTML(String tag, SimpleHTML parent) {
		this(tag, parent, null);
	}
	
	public SimpleHTML(String tag, SimpleHTML parent, List<Attr> attributes) {
		fTag = tag;
		fParent = parent;
		fAttrs = attributes;
	}
	
	public List<SimpleHTML> getChildren() {
		return fChildren;
	}
	
	public String getTag() {
		return fTag;
	}

	public String getText() {
		return fText;
	}
	
	private char nextChar(InputStream doc) throws IOException {
		int c = doc.read();
		
		if (c == -1)
			throw new IOException("EOS");
		
		return (char)c;
	}
	
	private void skipTo(String s, InputStream doc) throws IOException {
		int where = 0;
		char c;
		s = s.toLowerCase();
		
		while (where != s.length()) {
			 c = Character.toLowerCase(nextChar(doc));
			 if (c == s.charAt(where))
				 ++where;
			 else
				 where = 0;
		}
	}
	
	private List<Attr> parseAttributes(InputStream doc) throws IOException {
		List<Attr> attrs = new ArrayList<Attr>(4);
		StringBuilder txt;
		char c = nextChar(doc);
		String name, val;
		
		while (c != '>') {
			txt = new StringBuilder();
			while (Character.isWhitespace(c))
				c = nextChar(doc);
			
			while (c != '=' && !Character.isWhitespace(c) && c != '>') {
				txt.append(c);
				c = nextChar(doc);
			}

			if (Character.isWhitespace(c) || c == '>')
				continue;
			
			name = txt.toString();
			c = nextChar(doc);
			
			txt = new StringBuilder();
			
			if (c == '"') {
				c = nextChar(doc);
				while (c != '"') {
					txt.append(c);
					c = nextChar(doc);
				}
				c = nextChar(doc);
			} else if (c == '\'') {
				c = nextChar(doc);
				while (c != '\'') {
					txt.append(c);
					c = nextChar(doc);
				}
				c = nextChar(doc);
			} else {
				while (!Character.isWhitespace(c) && c != '>') {
					txt.append(c);
					c = nextChar(doc);
				}
			}
			val = txt.toString();
			attrs.add(new Attr(name, val));
		}
		
		return attrs;
	}
	
	private String getAttribute(String name, List<Attr> attrs) {
		for (Attr a : attrs)
			if (a.name.toLowerCase().equals(name))
				return a.value;
		
		return "";
	}
	
	public String parse(InputStream doc, SemitagHtmlPage page) {
		while (true)
		try {
			char c = nextChar(doc);
			char c2 = 0;
			boolean closing;
			List<Attr> attrs = null;

			StringBuilder txt = new StringBuilder();
			if ("script".equals(fTag)) {
				skipTo("</script>", doc);
				return null;
			}

			if ("style".equals(fTag)) {
				skipTo("</style>", doc);
				return null;
			}
			
			txt = new StringBuilder();
			while (Character.isWhitespace(c))
				c = nextChar(doc);
			
			while (c != '<') {
				txt.append(c);
				c = nextChar(doc);
			}
			
			if (txt.length() != 0)
				addText(txt.toString());
			txt = new StringBuilder();
			
			c = nextChar(doc);
			closing = c == '/';
			if (closing)
				c = nextChar(doc);
			
			while (!Character.isWhitespace(c) && c != '>') {
				txt.append(c);
				c2 = c;
				c = nextChar(doc);
			}
			
			if (txt.toString().equals("!--")) {
				skipTo("-->", doc);
				continue;
			}

			String tag = txt.toString().toLowerCase();
			
			if (c != '>' 
				&& ("form".equals(tag)
						|| "input".equals(tag)
						|| "option".equals(tag)
						|| "select".equals(tag))) {
				attrs = parseAttributes(doc);
				
				if ("form".equals(tag)) {
					page.setForm(new Form(getAttribute("action", attrs)));
				} else if ("input".equals(tag)) {
					page.getForm().addField(new FieldInput(getAttribute("name", attrs), getAttribute("value", attrs)));
				} else if ("select".equals(tag)) {
					page.getForm().addField(new FieldSelect(getAttribute("name", attrs)));
				}
				
				c2 = 0;
			} else {
				while (c != '>') {
					c2 = c;
					c = nextChar(doc);
				}
			}
			
			closing |= c2 == '/';

			
			if (closing) {
				if (fTag.equals(txt.toString().toLowerCase()))
					return null;
				else if (hasParentTag(txt.toString().toLowerCase()))
					return txt.toString();
				else
					continue;
			}
				
			SimpleHTML child = new SimpleHTML(tag, this, attrs);
			addChild(child);
			
			if ("meta".equals(tag) || "input".equals(tag) || "br".equals(tag))
				tag = null;
			else
				tag = child.parse(doc, page);
			
			if ("option".equals(child.fTag)) {
				page.getForm().addOption(getAttribute("value", attrs), child.fChildren.get(0).fText);
			}
			
			if (tag != null) {
				if (tag.toLowerCase().equals(fTag))
					return null;
				else
					return tag;
			}
			
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
		
	}
		
	private boolean hasParentTag(String tag) {
		SimpleHTML parent = fParent;
		tag = tag.toLowerCase();
		while (parent != null) {
			if (parent.fTag.equals(tag))
				return true;
			parent = parent.fParent;
		}
		
		return false;
	}

	public void addChild(SimpleHTML child) {
		fChildren.add(child);
	}
	
	public void addText(String txt) {
		SimpleHTML textNode = new SimpleHTML(null, this);
		textNode.setText(txt);
		addChild(textNode);
	}
	
	private void setText(String txt) {
		fText = txt;
	}

	private String display(int depth) {
		String res = new String();
		for (int i = 0; i < depth; ++i)
			res += "   ";
		
		res += "<" + fTag;
		
		if (fAttrs != null)
			for (Attr a: fAttrs)
				res += " " + a.name + "='" + a.value + "'";
		res += "> ";

		boolean firstText = true;
		for (SimpleHTML h: fChildren) { 
			if (h.fTag == null) {
				if (!firstText) {
					res += "\n";
					for (int i = 0; i < depth; ++i)
						res += "   ";
					
					res += "<..." + fTag + "> ";
				}
				res += "'" + h.fText + "'";
				firstText = false;
			} else {
				res += "\n";
				res += h.display(depth+1);
			}
		}

		return res;
	}
	
	public String toString() {
		return display(0);
	}

	public void cleanup() {
		boolean modified = true;

		for (SimpleHTML h: fChildren) {
			h.cleanup();
		}
		
		while (modified) {
			List<SimpleHTML> newChildren = new ArrayList<SimpleHTML>();
			modified = false;
			for (SimpleHTML h: fChildren) {
				if ("font".equals(h.fTag)
						|| "b".equals(h.fTag) 
						|| "p".equals(h.fTag) 
						|| "a".equals(h.fTag) 
						|| "i".equals(h.fTag) 
						|| "input".equals(h.fTag) 
						|| "form".equals(h.fTag) 
						|| "br".equals(h.fTag) 
						|| "small".equals(h.fTag) 
						|| "tr".equals(h.fTag) 
						|| "strong".equals(h.fTag)
						|| ("td".equals(h.fTag)
								&& h.fChildren.size() == 1
								&& "table".equals(h.fChildren.get(0).fTag))) {
					newChildren.addAll(h.fChildren);
					modified = true;
				} else if (h.fTag != null && h.fChildren.isEmpty() && h.fAttrs == null && !h.fTag.equals("table")) {
					modified = true;
				} else {
					if (h.fTag == null) {
						h.fText = h.fText.replace("&nbsp;", "");
						h.fText = h.fText.trim();
					}
					if (h.fTag != null || h.fText.length() != 0)
						newChildren.add(h);
					else
						modified = true;
				}
			}
			fChildren = newChildren;
		}
	}
}
